COMP3151/9154 Foundations of Concurrency
Term 2, 2022

Friday Code and Notes (Week 2)

Table of Contents

1 Promela code

1.1 Attempt 1

#define MutexDontCare
#include "critical2.h"

bit turn = 0;

proctype P() {
  do
  :: non_critical_section();
waitp: turn == 0;
csp: critical_section();
     turn = 1
  od
}
proctype Q() {
  do
  :: non_critical_section();
waitq: turn == 1;
csq: critical_section();
     turn = 0
  od
}

init { run P(); run Q(); }

ltl mutex { always !(P@csp && Q@csq)}
ltl waitp { [] (P@waitp implies (<> P@csp))  }
ltl waitq { [] (Q@waitq implies (<> Q@csq))  }

1.2 Attempt 2

#define MutexDontCare
#include "critical2.h"

bit wantp = 0;
bit wantq = 0;

proctype P() {
  do
  :: non_critical_section();
waitp: wantq == false;
     wantp = true;
csp: critical_section();
     wantp = false;
  od
}
proctype Q() {
  do
  :: non_critical_section();
waitq: wantp == false;
     wantq = true;
csq: critical_section();
     wantp = true;
  od
}

init { run P(); run Q(); }

ltl mutex { [] !(P@csp && Q@csq)}
ltl waitp { [] (P@waitp implies (eventually P@csp))  }
ltl waitq { [] (Q@waitq implies (<> Q@csq))  }

1.3 Attempt 3

You can do safety verification with "invalid endstates" enabled to find the deadlock here.

#define MutexDontCare
#include "critical2.h"

bit wantp = 0;
bit wantq = 0;

proctype P() {
  do
  :: non_critical_section();
     wantp = true;
waitp: wantq == false;
csp: critical_section();
     wantp = false;
  od
}
proctype Q() {
  do
  :: non_critical_section();
     wantq = true;
waitq: wantp == false;
csq: critical_section();
     wantq = false;
  od
}

init { run P(); run Q(); }

ltl mutex { [] !(P@csp && Q@csq)}
ltl waitp { [] (P@waitp implies (eventually P@csp))  }
ltl waitq { [] (Q@waitq implies (<> Q@csq))  }

1.4 Attempt 4

#define MutexDontCare
#include "critical2.h"

bit wantp = 0;
bit wantq = 0;

proctype P() {
  do
  :: non_critical_section();
waitp: wantp = true
     do
     :: wantq -> 
          wantp = false;
          wantp = true;
     :: else ->
          break
     od
csp: critical_section();
     wantp = false;
  od
}
proctype Q() {
  do
  :: non_critical_section();
waitq: wantq = true
     do
     :: wantp -> 
          wantq = false;
          wantq = true;
     :: else ->
          break
     od
csq: critical_section();
     wantq = false;
  od
}

init { run P(); run Q(); }

ltl mutex { [] !(P@csp && Q@csq)}
ltl waitp { [] (P@waitp implies (eventually P@csp))  }
ltl waitq { [] (Q@waitq implies (<> Q@csq))  }

1.5 Attempt 5

Attempt 5 is Dekker's algorithm, the first known correct solution to the critical section problem.

To verify eventual entry, you need to enable weak fairness to rule out traces where, after entering the pre-protocol, a process is never scheduled.

#define MutexDontCare
#include "critical2.h"

bit wantp = 0;
bit wantq = 0;
bit turn = 0;

proctype P() {
  do
  :: non_critical_section();
waitp: wantp = true
     do
     :: wantq -> 
       if
       :: turn == 1 -> 
            wantp = false;
            turn == 0;
            wantp = true;
       :: else -> skip
       fi
     :: else -> break
     od
csp: critical_section();
     turn = 1;
     wantp = false;
  od
}

proctype Q() {
  do
  :: non_critical_section();
waitq: wantq = true
     do
     :: wantp -> 
          if
          :: turn == 0 -> 
               wantq = false;
               turn == 1;
               wantq = true;
          :: else -> skip
          fi
     :: else -> break
     od
csq: critical_section();
     turn = 0;
     wantq = false;
  od
}

init { run P(); run Q(); }

ltl mutex { [] !(P@csp && Q@csq)}
ltl waitp { [] (P@waitp implies (eventually P@csp))  }
ltl waitq { [] (Q@waitq implies (<> Q@csq))  }

1.6 Critical section boilerplate

The above files use a critical2.h header file, whose contents are as follows.

/* 
  Definitions for critical section; derived from Ben-Ari's.

  If PID is defined, prints _pid in CS, otherwise prints a character parameter.

  If MutexDontCare is defined, no assertion is made about the number of
  processes in their CSs.
*/

#ifndef MutexDontCare
byte critical = 0; 
#endif
#define PID
#ifdef PID
inline critical_section() {
     printf("MSC: %d in CS\n", _pid);
#else
inline critical_section(proc) {
     printf("MSC: %c in CS\n", proc);
#endif
#ifndef MutexDontCare
     critical++;
     assert (critical == 1);
     critical--;
#endif
}

/* 
  Definitions for non-critical sections to complement M Ben-Ari's
  critical.h. If PID is defined, prints _pid in non-CS, otherwise
  prints its mandatory character argument.
*/

#ifdef PID
inline non_critical_section() {
  printf("MSC: %d in non-CS\n", _pid);
#else
inline non_critical_section(proc) {
  printf("MSC: %c in non-CS\n", proc);
#endif
  do                            /* non-deterministically choose how
                                   long to stay, even forever */
    :: true ->
         skip
    :: true ->
         break
  od
}

2 Notes

In a transition label

  g; f

when g = ⊤, we just write f

When there's no state update (when f = I "the identity function"),
then we write g

Q: do we allow Σ to be infinite? Yes.

Σ is the set of all possible states.

What is a state?
A: we don't bother specifying, because it's application dependent
  usually, at least:
  - a mapping from variable names to values
  - which location every process is at

Q: when we change the state, we also update the location, right?
   or: when we change the location, we change the state?
A: yes


== Floyd's method on the first transition diagram

P stands for the whole transition diagram.
   aka the triangular number program

We want to prove this Hoare triple:

  {⊤} P {s = N*(N-1)/2}

We must find an *annotation* for every location.
The annotation should state something that's
always true when we're at that location.

1 The precondition implies the
  start location's annotation
2 The exit location's annotation
  implies the postcondition.
3 If the current location's annotation is true,
  then if we take a transition, the next
  location's annotation becomes true.

Annotations (this is called an *assertion network*)

l0: ⊤
l1: i = 0
l2: s = i*(i-1)/2
l3: s = i*(i+1)/2
l4: s = N*(N-1)/2

Observe: the first two points above are trivial

Examples of point 3.

Consider the transition from l1 to l2.
We must prove
  i = 0 ⇒ (s = i*(i-1)/2)[s:=0]

that is:

  i = 0 ⇒ (0 = i*(i-1)/2)  <-- trivial

Consider the transition from l2 to l3
We must prove

  s = i*(i-1)/2 ∧ i ≠ N ⇒ s+i = i*(i+1)/2

You can prove this with some basic arithmetic


Q(l_i) ∧ g ⇒ Q(l_j) o f

o denotes function composition.

(f o g)(x) = f(g(x))

"If the annotation at l_i is true,
 and the guard is true,
 then, after updating the state with f,
 the annotation at l_j is true"



p1: wantp = 0                   
p2: wantp = 0
p3: wantp ≠ 0
p4: wantp ≠ 0 ∧ wantq ≠ wantp
p5: ⊤

q1: wantq = 0                   
q2: wantq = 0
q3: wantq ≠ 0
q4: wantq ≠ 0 ∧ wantp ≠ -wantq
q5: ⊤

Step 1: check that these annotations are inductive (separately)
Step 2: check for interference

  Suppose P is sitting at location p1. Therefore, wantp=0.
  We need to check that no action that Q could take
  falsifies wantp=0. Trivial because Q never changes wantp.

  Suppose instead P is sitting at p4.

   wantp ≠ 0 ∧ wantq ≠ wantp

  We need to check that nothing Q can do falsifies this annotation.
  There are two actions that are relevant are q2 and q5.

  For q5:

    wantp ≠ 0 ∧ wantq ≠ wantp ∧ ⊤ ⇒ wantp ≠ 0 ∧ 0 ≠ wantp (trivial)

  For q2:

    wantp ≠ 0 ∧ wantq ≠ wantp ∧ wantq = 0 ∧ wantp = -1 ⇒ wantp ≠ 0 ∧ 1 ≠ wantp
   ..and one more.


One final step: mutual exclusion holds.
  Show that  the annotations of p4 and q4 cannot both be true.
  Not possible because if so, wantp and wantq would have
  simultaneously different and equal polarity.

2022-08-05 Fri 16:47

Announcements RSS